package com.google.code.stalkernet.async.io;

import java.io.IOException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;

import com.google.code.stalkernet.reactor.Reactor;
import com.google.code.stalkernet.reactor.Reactor.SelectableChannelCallback;

/**
 * Abstract base class for asynchronous wrappers of {@link SelectableChannel}
 * subclasses.
 *
 * @author Mikhail Vladimirov
 */
public abstract class AbstractAsyncSelectableChannel
{
    private final MySelectableChannelCallback mySelectableChannelCallback =
        new MySelectableChannelCallback ();

    private final SelectionKey selectionKey;

    /**
     * Create new asynchronous selectable channel wrapping given
     * {@link SelectableChannel}.
     *
     * @param selectableChannel {@link SelectableChannel} to wrap
     * @param reactor {@link Reactor} to use
     * @throws IOException if I/O error occurred
     */
    public AbstractAsyncSelectableChannel (
        SelectableChannel selectableChannel, Reactor reactor)
        throws IOException
    {
        if (selectableChannel == null)
            throw new IllegalArgumentException ("Selectable channel is null");

        if (reactor == null)
            throw new IllegalArgumentException ("Reactor is null");

        selectableChannel.configureBlocking (false);

        selectionKey = reactor.registerSelectableChannel (
            selectableChannel, mySelectableChannelCallback);
    }

    /**
     * Tells whether we want to perform accept operation on
     * {@link SelectableChannel}.
     *
     * @return <code>true</code> if we want to perform accept operation,
     *         <code>false</code> otherwise
     */
    protected abstract boolean wantAccept ();

    /**
     * Called when accept operation can be performed on
     * {@link SelectableChannel}.
     */
    protected abstract void canAccept ();

    /**
     * Tells whether we want to perform connect operation on
     * {@link SelectableChannel}.
     *
     * @return <code>true</code> if we want to perform accept operation,
     *         <code>false</code> otherwise
     */
    protected abstract boolean wantConnect ();

    /**
     * Called when connect operation can be performed on
     * {@link SelectableChannel}.
     */
    protected abstract void canConnect ();

    /**
     * Tells whether we want to perform read operation on
     * {@link SelectableChannel}.
     *
     * @return <code>true</code> if we want to perform accept operation,
     *         <code>false</code> otherwise
     */
    protected abstract boolean wantRead ();

    /**
     * Called when read operation can be performed on
     * {@link SelectableChannel}.
     */
    protected abstract void canRead ();

    /**
     * Tells whether we want to perform write operation on
     * {@link SelectableChannel}.
     *
     * @return <code>true</code> if we want to perform accept operation,
     *         <code>false</code> otherwise
     */
    protected abstract boolean wantWrite ();

    /**
     * Called when write operation can be performed on
     * {@link SelectableChannel}.
     */
    protected abstract void canWrite ();

    /**
     * Update interest operations set for {@link SelectableChannel}.
     */
    protected void updateInterestOps ()
    {
        if (selectionKey.isValid ())
            selectionKey.interestOps (
                (wantAccept () ? SelectionKey.OP_ACCEPT : 0) |
                (wantConnect () ? SelectionKey.OP_CONNECT : 0) |
                (wantRead () ? SelectionKey.OP_READ : 0) |
                (wantWrite () ? SelectionKey.OP_WRITE : 0));
    }

    private class MySelectableChannelCallback
        implements SelectableChannelCallback
    {
        @Override
        public void selected (SelectionKey selectionKey)
        {
            if (selectionKey == null)
                throw new IllegalArgumentException ("Selection key is null");

            if (selectionKey.isValid () && wantAccept () &&
                selectionKey.isAcceptable ()) canAccept ();

            if (selectionKey.isValid () && wantConnect () &&
                selectionKey.isConnectable ()) canConnect ();

            if (selectionKey.isValid () && wantRead () &&
                selectionKey.isReadable ()) canRead ();

            if (selectionKey.isValid () && wantWrite () &&
                selectionKey.isWritable ()) canWrite ();

            updateInterestOps ();
        }
    }
}
